/**
 * SHACrypt main code
 *
 * SHACrypt - Encrypt files using cryptographic hash functions
 * Copyright (C) 2010 LlamaSlayers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @since 1.1.2
 * @version 1.2.2
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "shacrypt.h"

#include "third-party/sha1.h"
#include "third-party/sha2.h"
#include "third-party/md5.h"
#include "third-party/cubehash.h"
#include "third-party/nessie.h"
#include "third-party/mt19937ar.h"

#ifdef WINDOWS
#include <windows.h>
#include <wincrypt.h>
#endif

int SHACrypt_XOR( char* in1, char* in2, char* out, int len ) {
    int i = 0;

    for ( i = 0; i < len; i++ ) {
        out[i] = (char)(in1[i] ^ in2[i]);
    }

    return 0;
}

int SHACrypt_GenKey( char* oldkey, char* newkey, char* password ) {
    int status = 0;
    SHA1Context sha;

    status = SHA1Reset( &sha );
    if ( status )
        return status;

    status = SHA1Input( &sha, (unsigned char*)oldkey, sizeof( oldkey ) );
    if ( status )
        return status;

    status = SHA1Input( &sha, (unsigned char*)password, sizeof( password ) );
    if ( status )
        return status;

    status = SHA1Result( &sha, (unsigned char*)newkey );

    return status;
}

int SHACrypt_GenRand( char* data, int len ) {
    FILE* rnd = fopen( "/dev/urandom", "rb" );
    int i;

#ifdef WINDOWS
    HCRYPTPROV hCryptProv;
    BYTE pbData[len];

    if ( CryptAcquireContext( &hCryptProv, NULL, NULL, PROV_RSA_FULL, 0 ) ) {
        if ( CryptGenRandom( hCryptProv, (DWORD)len, pbData ) ) {
            for ( i = 0; i < len; i++ ) {
                data[i] = (char)pbData[i];
            }
            return 0;
        }
        CryptReleaseContext( hCryptProv, 0 );
    }
#endif

    if ( rnd ) {
        fread( data, 1, len, rnd );
        fclose( rnd );
        return 0;
    }

    // Time-based random number generators aren't the best possible choice,
    // but it's all we have left.
    mt_init_genrand( time( NULL ) );

    char rnd1[20] = {0},
         rnd2[20] = {0};
    unsigned long rnd3[15];

    // 1st pass
    for ( i = 0; i < len; i++ ) {
        data[i] = (char)( mt_genrand_int32() & 0xFF );
    }
    // 2nd-5th pass
    for ( i = 0; i < 15; i++ ) {
        SHACrypt_GenKey( rnd1, rnd2, data );
        for ( ; i % 5 != 4; i++ ) {
            rnd3[i] = ( rnd2[i % 5] << 24 ) + ( rnd2[( i % 5 ) + 1] << 16 ) +
                      ( rnd2[( i % 5 ) + 2] << 8 ) + ( rnd2[( i % 5 ) + 3] );
        }
    }
    // 6th pass
    mt_init_by_array( rnd3, 15 );
    for ( i = 0; i < len; i++ ) {
        data[i] = (char)( mt_genrand_int32() & 0xFF );
    }

    return 0;
}

int SHACrypt_GetLength( unsigned char algos ) {
    if ( algos & '\xD0' )
        return 64;
    if ( algos & '\x08' )
        return 48;
    if ( algos & '\x04' )
        return 32;
    if ( algos & '\x02' )
        return 28;
    if ( algos & '\x01' )
        return 20;
    if ( algos & '\x20' )
        return 16;
    return 0;
}

int SHACrypt_GetRand( unsigned char algos, char *rand ) {
    int len = SHACrypt_GetLength( algos );
    SHACrypt_GenRand( rand, len );
    return len;
}

void SHACrypt_Init( struct SHACrypt_Context *ctx, unsigned char *password, unsigned int passlen,
                    unsigned char algos, unsigned char *rand, unsigned int randlen ) {
    ctx->algos    = algos;
    ctx->password = password;
    ctx->passlen  = passlen;
    ctx->rand     = rand;
    ctx->randlen  = randlen;

    if ( algos & '\x01' ) {
        ctx->sha1_ptr = 0;
        SHA1Reset( &ctx->sha1 );
        SHA1Input( &ctx->sha1, rand, randlen );
        SHA1Input( &ctx->sha1, password, passlen );
        SHA1Result( &ctx->sha1, ctx->sha1_res );
    }

    if ( algos & '\x02' ) {
        ctx->sha224_ptr = 0;
        sha224_init( &ctx->sha224 );
        sha224_update( &ctx->sha224, rand, randlen );
        sha224_update( &ctx->sha224, password, passlen );
        sha224_final( &ctx->sha224, ctx->sha224_res );
    }

    if ( algos & '\x04' ) {
        ctx->sha256_ptr = 0;
        sha256_init( &ctx->sha256 );
        sha256_update( &ctx->sha256, rand, randlen );
        sha256_update( &ctx->sha256, password, passlen );
        sha256_final( &ctx->sha256, ctx->sha256_res );
    }

    if ( algos & '\x08' ) {
        ctx->sha384_ptr = 0;
        sha384_init( &ctx->sha384 );
        sha384_update( &ctx->sha384, rand, randlen );
        sha384_update( &ctx->sha384, password, passlen );
        sha384_final( &ctx->sha384, ctx->sha384_res );
    }

    if ( algos & '\x10' ) {
        ctx->sha512_ptr = 0;
        sha512_init( &ctx->sha512 );
        sha512_update( &ctx->sha512, rand, randlen );
        sha512_update( &ctx->sha512, password, passlen );
        sha512_final( &ctx->sha512, ctx->sha512_res );
    }

    if ( algos & '\x20' ) {
        ctx->md5_ptr = 0;
        MD5Init( &ctx->md5 );
        MD5Update( &ctx->md5, rand, randlen );
        MD5Update( &ctx->md5, password, passlen );
        MD5Final( ctx->md5_res, &ctx->md5 );
    }

    if ( algos & '\x40' ) {
        ctx->cube_ptr = 0;
        CubeHash_Init( &ctx->cube, 512 );
        CubeHash_Update( &ctx->cube, rand, randlen << 2 );
        CubeHash_Update( &ctx->cube, password, passlen << 2 );
        CubeHash_Final( &ctx->cube, ctx->cube_res );
    }

    if ( algos & '\x80' ) {
        ctx->whirlpool_ptr = 0;
        NESSIEinit( &ctx->whirlpool );
        NESSIEadd( rand, randlen << 3, &ctx->whirlpool );
        NESSIEadd( password, passlen << 3, &ctx->whirlpool );
        NESSIEfinalize( &ctx->whirlpool, ctx->whirlpool_res );
    }
}

void SHACrypt_Process( SHACrypt_Context *ctx, char *in, char *out, unsigned int len ) {
    int i;
    char c;

    for ( i = 0; i < len; i++ ) {
        c = in[i];

        if ( ctx->algos & '\x01' ) {
            if ( ctx->sha1_ptr > 19 ) {
                ctx->sha1_ptr = 0;
                SHA1Reset( &ctx->sha1 );
                SHA1Input( &ctx->sha1, ctx->sha1_res, 20 );
                SHA1Input( &ctx->sha1, ctx->password, ctx->passlen );
                SHA1Result( &ctx->sha1, ctx->sha1_res );
            }
            c ^= ctx->sha1_res[ctx->sha1_ptr];
            ctx->sha1_ptr++;
        }

        if ( ctx->algos & '\x02' ) {
            if ( ctx->sha224_ptr > 27 ) {
                ctx->sha224_ptr = 0;
                sha224_init( &ctx->sha224 );
                sha224_update( &ctx->sha224, ctx->sha224_res, 28 );
                sha224_update( &ctx->sha224, ctx->password, ctx->passlen );
                sha224_final( &ctx->sha224, ctx->sha224_res );
            }
            c ^= ctx->sha224_res[ctx->sha224_ptr];
            ctx->sha224_ptr++;
        }

        if ( ctx->algos & '\x04' ) {
            if ( ctx->sha256_ptr > 31 ) {
                ctx->sha256_ptr = 0;
                sha256_init( &ctx->sha256 );
                sha256_update( &ctx->sha256, ctx->sha256_res, 32 );
                sha256_update( &ctx->sha256, ctx->password, ctx->passlen );
                sha256_final( &ctx->sha256, ctx->sha256_res );
            }
            c ^= ctx->sha256_res[ctx->sha256_ptr];
            ctx->sha256_ptr++;
        }

        if ( ctx->algos & '\x08' ) {
            if ( ctx->sha384_ptr > 47 ) {
                ctx->sha384_ptr = 0;
                sha384_init( &ctx->sha384 );
                sha384_update( &ctx->sha384, ctx->sha384_res, 48 );
                sha384_update( &ctx->sha384, ctx->password, ctx->passlen );
                sha384_final( &ctx->sha384, ctx->sha384_res );
            }
            c ^= ctx->sha384_res[ctx->sha384_ptr];
            ctx->sha384_ptr++;
        }

        if ( ctx->algos & '\x10' ) {
            if ( ctx->sha512_ptr > 63 ) {
                ctx->sha512_ptr = 0;
                sha512_init( &ctx->sha512 );
                sha512_update( &ctx->sha512, ctx->sha512_res, 64 );
                sha512_update( &ctx->sha512, ctx->password, ctx->passlen );
                sha512_final( &ctx->sha512, ctx->sha512_res );
            }
            c ^= ctx->sha512_res[ctx->sha512_ptr];
            ctx->sha512_ptr++;
        }

        if ( ctx->algos & '\x20' ) {
            if ( ctx->md5_ptr > 15 ) {
                ctx->md5_ptr = 0;
                MD5Init( &ctx->md5 );
                MD5Update( &ctx->md5, ctx->md5_res, 16 );
                MD5Update( &ctx->md5, ctx->password, ctx->passlen );
                MD5Final( ctx->md5_res, &ctx->md5 );
            }
            c ^= ctx->md5_res[ctx->md5_ptr];
            ctx->md5_ptr++;
        }

        if ( ctx->algos & '\x40' ) {
            if ( ctx->cube_ptr > 63 ) {
                ctx->cube_ptr = 0;
                CubeHash_Init( &ctx->cube, 512 );
                CubeHash_Update( &ctx->cube, ctx->cube_res, 512 );
                CubeHash_Update( &ctx->cube, ctx->password, ctx->passlen * 8 );
                CubeHash_Final( &ctx->cube, ctx->cube_res );
            }
            c ^= ctx->cube_res[ctx->cube_ptr];
            ctx->cube_ptr++;
        }

        if ( ctx->algos & '\x80' ) {
            if ( ctx->whirlpool_ptr > 63 ) {
                ctx->whirlpool_ptr = 0;
                NESSIEinit( &ctx->whirlpool );
                NESSIEadd( ctx->whirlpool_res, 512, &ctx->whirlpool );
                NESSIEadd( ctx->password, ctx->passlen * 8, &ctx->whirlpool );
                NESSIEfinalize( &ctx->whirlpool, ctx->whirlpool_res );
            }
            c ^= ctx->whirlpool_res[ctx->whirlpool_ptr];
            ctx->whirlpool_ptr++;
        }

        out[i] = c;
    }
}

/**
 * @since 1.2.2
 * @version 1.2.2
 */
void SHACrypt_SetScramble( SHACrypt_Context *ctx, unsigned char *code, unsigned int len ) {
    ctx->scramblekey = code;
    ctx->scramblelen = len;

    if ( ctx->algos & '\x01' ) {
        ctx->scramble_sha1_ptr = 0;
        SHA1Reset( &ctx->scramble_sha1 );
        SHA1Input( &ctx->scramble_sha1, ctx->rand, ctx->randlen );
        SHA1Input( &ctx->scramble_sha1, code, len );
        SHA1Result( &ctx->scramble_sha1, ctx->scramble_sha1_res );
    }

    if ( ctx->algos & '\x02' ) {
        ctx->scramble_sha224_ptr = 0;
        sha224_init( &ctx->scramble_sha224 );
        sha224_update( &ctx->scramble_sha224, ctx->rand, ctx->randlen );
        sha224_update( &ctx->scramble_sha224, code, len );
        sha224_final( &ctx->scramble_sha224, ctx->scramble_sha224_res );
    }

    if ( ctx->algos & '\x04' ) {
        ctx->scramble_sha256_ptr = 0;
        sha256_init( &ctx->scramble_sha256 );
        sha256_update( &ctx->scramble_sha256, ctx->rand, ctx->randlen );
        sha256_update( &ctx->scramble_sha256, code, len );
        sha256_final( &ctx->scramble_sha256, ctx->scramble_sha256_res );
    }

    if ( ctx->algos & '\x08' ) {
        ctx->scramble_sha384_ptr = 0;
        sha384_init( &ctx->scramble_sha384 );
        sha384_update( &ctx->scramble_sha384, ctx->rand, ctx->randlen );
        sha384_update( &ctx->scramble_sha384, code, len );
        sha384_final( &ctx->scramble_sha384, ctx->scramble_sha384_res );
    }

    if ( ctx->algos & '\x10' ) {
        ctx->scramble_sha512_ptr = 0;
        sha512_init( &ctx->scramble_sha512 );
        sha512_update( &ctx->scramble_sha512, ctx->rand, ctx->randlen );
        sha512_update( &ctx->scramble_sha512, code, len );
        sha512_final( &ctx->scramble_sha512, ctx->scramble_sha512_res );
    }

    if ( ctx->algos & '\x20' ) {
        ctx->scramble_md5_ptr = 0;
        MD5Init( &ctx->scramble_md5 );
        MD5Update( &ctx->scramble_md5, ctx->rand, ctx->randlen );
        MD5Update( &ctx->scramble_md5, code, len );
        MD5Final( ctx->scramble_md5_res, &ctx->scramble_md5 );
    }

    if ( ctx->algos & '\x40' ) {
        ctx->scramble_cube_ptr = 0;
        CubeHash_Init( &ctx->scramble_cube, 512 );
        CubeHash_Update( &ctx->scramble_cube, ctx->rand, (int)ctx->randlen << 2 );
        CubeHash_Update( &ctx->scramble_cube, code, (int)len << 2 );
        CubeHash_Final( &ctx->scramble_cube, ctx->scramble_cube_res );
    }

    if ( ctx->algos & '\x80' ) {
        ctx->scramble_whirlpool_ptr = 0;
        NESSIEinit( &ctx->scramble_whirlpool );
        NESSIEadd( ctx->rand, (int)ctx->randlen << 3, &ctx->scramble_whirlpool );
        NESSIEadd( code, (int)len << 3, &ctx->scramble_whirlpool );
        NESSIEfinalize( &ctx->scramble_whirlpool, ctx->scramble_whirlpool_res );
    }
}

/**
 * @since 1.2.2
 * @version 1.2.2
 */
void SHACrypt_Scramble( SHACrypt_Context* ctx, char* in, char* out, unsigned int len ) {
    unsigned int i;
    unsigned char c,
                  tmp,
                  buf[256] = {0};

    // First pass: Just copy
    for ( i = 0; i < len; i++ ) {
        out[i] = in[i];
    }

    // Second pass: Make the buffer
    for ( i = 0; i < len; i++ ) {
        c = 0;

        if ( ctx->algos & '\x01' ) {
            if ( ctx->scramble_sha1_ptr > 19 ) {
                ctx->scramble_sha1_ptr = 0;
                SHA1Reset( &ctx->scramble_sha1 );
                SHA1Input( &ctx->scramble_sha1, ctx->scramble_sha1_res, 20 );
                SHA1Input( &ctx->scramble_sha1, ctx->scramblekey, ctx->scramblelen );
                SHA1Result( &ctx->scramble_sha1, ctx->scramble_sha1_res );
            }
            c ^= ctx->scramble_sha1_res[ctx->scramble_sha1_ptr];
            ctx->scramble_sha1_ptr++;
        }

        if ( ctx->algos & '\x02' ) {
            if ( ctx->scramble_sha224_ptr > 27 ) {
                ctx->scramble_sha224_ptr = 0;
                sha224_init( &ctx->scramble_sha224 );
                sha224_update( &ctx->scramble_sha224, ctx->scramble_sha224_res, 28 );
                sha224_update( &ctx->scramble_sha224, ctx->scramblekey, ctx->scramblelen );
                sha224_final( &ctx->scramble_sha224, ctx->scramble_sha224_res );
            }
            c ^= ctx->scramble_sha224_res[ctx->scramble_sha224_ptr];
            ctx->scramble_sha224_ptr++;
        }

        if ( ctx->algos & '\x04' ) {
            if ( ctx->scramble_sha256_ptr > 31 ) {
                ctx->scramble_sha256_ptr = 0;
                sha256_init( &ctx->scramble_sha256 );
                sha256_update( &ctx->scramble_sha256, ctx->scramble_sha256_res, 32 );
                sha256_update( &ctx->scramble_sha256, ctx->scramblekey, ctx->scramblelen );
                sha256_final( &ctx->scramble_sha256, ctx->scramble_sha256_res );
            }
            c ^= ctx->scramble_sha256_res[ctx->scramble_sha256_ptr];
            ctx->scramble_sha256_ptr++;
        }

        if ( ctx->algos & '\x08' ) {
            if ( ctx->scramble_sha384_ptr > 47 ) {
                ctx->scramble_sha384_ptr = 0;
                sha384_init( &ctx->scramble_sha384 );
                sha384_update( &ctx->scramble_sha384, ctx->scramble_sha384_res, 48 );
                sha384_update( &ctx->scramble_sha384, ctx->scramblekey, ctx->scramblelen );
                sha384_final( &ctx->scramble_sha384, ctx->scramble_sha384_res );
            }
            c ^= ctx->scramble_sha384_res[ctx->scramble_sha384_ptr];
            ctx->scramble_sha384_ptr++;
        }

        if ( ctx->algos & '\x10' ) {
            if ( ctx->scramble_sha512_ptr > 63 ) {
                ctx->scramble_sha512_ptr = 0;
                sha512_init( &ctx->scramble_sha512 );
                sha512_update( &ctx->scramble_sha512, ctx->scramble_sha512_res, 64 );
                sha512_update( &ctx->scramble_sha512, ctx->scramblekey, ctx->scramblelen );
                sha512_final( &ctx->scramble_sha512, ctx->scramble_sha512_res );
            }
            c ^= ctx->scramble_sha512_res[ctx->scramble_sha512_ptr];
            ctx->scramble_sha512_ptr++;
        }

        if ( ctx->algos & '\x20' ) {
            if ( ctx->scramble_md5_ptr > 15 ) {
                ctx->scramble_md5_ptr = 0;
                MD5Init( &ctx->scramble_md5 );
                MD5Update( &ctx->scramble_md5, ctx->scramble_md5_res, 16 );
                MD5Update( &ctx->scramble_md5, ctx->scramblekey, ctx->scramblelen );
                MD5Final( ctx->scramble_md5_res, &ctx->scramble_md5 );
            }
            c ^= ctx->scramble_md5_res[ctx->scramble_md5_ptr];
            ctx->scramble_md5_ptr++;
        }

        if ( ctx->algos & '\x40' ) {
            if ( ctx->scramble_cube_ptr > 63 ) {
                ctx->scramble_cube_ptr = 0;
                CubeHash_Init( &ctx->scramble_cube, 512 );
                CubeHash_Update( &ctx->scramble_cube, ctx->scramble_cube_res, 512 );
                CubeHash_Update( &ctx->scramble_cube, ctx->scramblekey, (int)ctx->scramblelen << 3 );
                CubeHash_Final( &ctx->scramble_cube, ctx->scramble_cube_res );
            }
            c ^= ctx->scramble_cube_res[ctx->scramble_cube_ptr];
            ctx->scramble_cube_ptr++;
        }

        if ( ctx->algos & '\x80' ) {
            if ( ctx->scramble_whirlpool_ptr > 63 ) {
                ctx->scramble_whirlpool_ptr = 0;
                NESSIEinit( &ctx->scramble_whirlpool );
                NESSIEadd( ctx->scramble_whirlpool_res, 512, &ctx->scramble_whirlpool );
                NESSIEadd( ctx->scramblekey, (int)ctx->scramblelen << 3, &ctx->scramble_whirlpool );
                NESSIEfinalize( &ctx->scramble_whirlpool, ctx->scramble_whirlpool_res );
            }
            c ^= ctx->scramble_whirlpool_res[ctx->scramble_whirlpool_ptr];
            ctx->scramble_whirlpool_ptr++;
        }

        buf[i] = c;
    }

    // Third pass: The actual swapping
    for ( i = 0; i < len; i++ ) {
        c = buf[i];
        tmp = out[i];
        out[i] = out[c % len];
        out[c % len] = tmp;
    }
}

/**
 * @since 1.2.2
 * @version 1.2.2
 */
void SHACrypt_Unscramble( SHACrypt_Context* ctx, char* in, char* out, unsigned int len ) {
    unsigned int i;
    unsigned char c,
                  tmp,
                  buf[256] = {0};

    // First pass: Just copy
    for ( i = 0; i < len; i++ ) {
        out[i] = in[i];
    }

    // Second pass: Make the buffer
    for ( i = 0; i < len; i++ ) {
        c = 0;

        if ( ctx->algos & '\x01' ) {
            if ( ctx->scramble_sha1_ptr > 19 ) {
                ctx->scramble_sha1_ptr = 0;
                SHA1Reset( &ctx->scramble_sha1 );
                SHA1Input( &ctx->scramble_sha1, ctx->scramble_sha1_res, 20 );
                SHA1Input( &ctx->scramble_sha1, ctx->scramblekey, ctx->scramblelen );
                SHA1Result( &ctx->scramble_sha1, ctx->scramble_sha1_res );
            }
            c ^= ctx->scramble_sha1_res[ctx->scramble_sha1_ptr];
            ctx->scramble_sha1_ptr++;
        }

        if ( ctx->algos & '\x02' ) {
            if ( ctx->scramble_sha224_ptr > 27 ) {
                ctx->scramble_sha224_ptr = 0;
                sha224_init( &ctx->scramble_sha224 );
                sha224_update( &ctx->scramble_sha224, ctx->scramble_sha224_res, 28 );
                sha224_update( &ctx->scramble_sha224, ctx->scramblekey, ctx->scramblelen );
                sha224_final( &ctx->scramble_sha224, ctx->scramble_sha224_res );
            }
            c ^= ctx->scramble_sha224_res[ctx->scramble_sha224_ptr];
            ctx->scramble_sha224_ptr++;
        }

        if ( ctx->algos & '\x04' ) {
            if ( ctx->scramble_sha256_ptr > 31 ) {
                ctx->scramble_sha256_ptr = 0;
                sha256_init( &ctx->scramble_sha256 );
                sha256_update( &ctx->scramble_sha256, ctx->scramble_sha256_res, 32 );
                sha256_update( &ctx->scramble_sha256, ctx->scramblekey, ctx->scramblelen );
                sha256_final( &ctx->scramble_sha256, ctx->scramble_sha256_res );
            }
            c ^= ctx->scramble_sha256_res[ctx->scramble_sha256_ptr];
            ctx->scramble_sha256_ptr++;
        }

        if ( ctx->algos & '\x08' ) {
            if ( ctx->scramble_sha384_ptr > 47 ) {
                ctx->scramble_sha384_ptr = 0;
                sha384_init( &ctx->scramble_sha384 );
                sha384_update( &ctx->scramble_sha384, ctx->scramble_sha384_res, 48 );
                sha384_update( &ctx->scramble_sha384, ctx->scramblekey, ctx->scramblelen );
                sha384_final( &ctx->scramble_sha384, ctx->scramble_sha384_res );
            }
            c ^= ctx->scramble_sha384_res[ctx->scramble_sha384_ptr];
            ctx->scramble_sha384_ptr++;
        }

        if ( ctx->algos & '\x10' ) {
            if ( ctx->scramble_sha512_ptr > 63 ) {
                ctx->scramble_sha512_ptr = 0;
                sha512_init( &ctx->scramble_sha512 );
                sha512_update( &ctx->scramble_sha512, ctx->scramble_sha512_res, 64 );
                sha512_update( &ctx->scramble_sha512, ctx->scramblekey, ctx->scramblelen );
                sha512_final( &ctx->scramble_sha512, ctx->scramble_sha512_res );
            }
            c ^= ctx->scramble_sha512_res[ctx->scramble_sha512_ptr];
            ctx->scramble_sha512_ptr++;
        }

        if ( ctx->algos & '\x20' ) {
            if ( ctx->scramble_md5_ptr > 15 ) {
                ctx->scramble_md5_ptr = 0;
                MD5Init( &ctx->scramble_md5 );
                MD5Update( &ctx->scramble_md5, ctx->scramble_md5_res, 16 );
                MD5Update( &ctx->scramble_md5, ctx->scramblekey, ctx->scramblelen );
                MD5Final( ctx->scramble_md5_res, &ctx->scramble_md5 );
            }
            c ^= ctx->scramble_md5_res[ctx->scramble_md5_ptr];
            ctx->scramble_md5_ptr++;
        }

        if ( ctx->algos & '\x40' ) {
            if ( ctx->scramble_cube_ptr > 63 ) {
                ctx->scramble_cube_ptr = 0;
                CubeHash_Init( &ctx->scramble_cube, 512 );
                CubeHash_Update( &ctx->scramble_cube, ctx->scramble_cube_res, 512 );
                CubeHash_Update( &ctx->scramble_cube, ctx->scramblekey, (int)ctx->scramblelen << 3 );
                CubeHash_Final( &ctx->scramble_cube, ctx->scramble_cube_res );
            }
            c ^= ctx->scramble_cube_res[ctx->scramble_cube_ptr];
            ctx->scramble_cube_ptr++;
        }

        if ( ctx->algos & '\x80' ) {
            if ( ctx->scramble_whirlpool_ptr > 63 ) {
                ctx->scramble_whirlpool_ptr = 0;
                NESSIEinit( &ctx->scramble_whirlpool );
                NESSIEadd( ctx->scramble_whirlpool_res, 512, &ctx->scramble_whirlpool );
                NESSIEadd( ctx->scramblekey, (int)ctx->scramblelen << 3, &ctx->scramble_whirlpool );
                NESSIEfinalize( &ctx->scramble_whirlpool, ctx->scramble_whirlpool_res );
            }
            c ^= ctx->scramble_whirlpool_res[ctx->scramble_whirlpool_ptr];
            ctx->scramble_whirlpool_ptr++;
        }

        buf[i] = c;
    }

    // Third pass: The actual swapping
    for ( i = len - 1; i > 0; i-- ) {
        c = buf[i];
        tmp = out[i];
        out[i] = out[c % len];
        out[c % len] = tmp;
    }
    c = buf[0];
    tmp = out[0];
    out[0] = out[c % len];
    out[c % len] = tmp;
}
